﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Enemy.cs" company="">
//   
// </copyright>
// <summary>
// A foe to kill.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

/*
 * Authors: Juan Mompean Esteban, Mikkel Thordal
 * Created: 17-11-2011
 */

namespace ITU_vs_Zombies.Character
{
    using System;
    using System.Diagnostics.Contracts;

    using ITU_vs_Zombies.Game;
    using ITU_vs_Zombies.Graphics;
    using ITU_vs_Zombies.Movement;
    using ITU_vs_Zombies.Scenery;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    [Serializable]
    public class Enemy : IGraphicsHelper
    {
        #region Constants and Fields

        /// <summary>
        /// The death sound.
        /// </summary>
        [NonSerialized]
        public SoundEffect DeathSound;

        /// <summary>
        /// The texture.
        /// </summary>
        [NonSerialized]
        public Texture2D Texture;

        /// <summary>
        /// The damage.
        /// </summary>
        private readonly uint damage;

        /// <summary>
        /// Gets or sets Distance.
        /// </summary>
        private readonly uint distance;

        /// <summary>
        /// Gets or sets NumberFloor.
        /// </summary>
        private readonly int numberFloor;

        /// <summary>
        /// The number frames.
        /// </summary>
        private readonly uint numberFrames;

        /// <summary>
        /// The speed.
        /// </summary>
        private readonly uint speed;

        /// <summary>
        /// The time to attack.
        /// </summary>
        private readonly TimeSpan timeToAttack;

        /// <summary>
        /// The last attack.
        /// </summary>
        private TimeSpan lastAttack;

        /// <summary>
        /// The last screen position.
        /// </summary>
        private Vector2 lastScreenPosition;

        /// <summary>
        /// The life.
        /// </summary>
        private uint life;

        /// <summary>
        /// The maximum life.
        /// </summary>
        private uint maximumLife;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Enemy"/> class.
        /// </summary>
        /// <param name="life">
        /// The maximum amount of life of the enemy.
        /// </param>
        /// <param name="image">
        /// The image that represents to the enemy.
        /// </param>
        /// <param name="numberFrames">
        /// The number Frames of the image of this enemy.
        /// </param>
        /// <param name="damage">
        /// The damage that this enemy can do.
        /// </param>
        /// <param name="distance">
        /// How far can this enemy attack.
        /// </param>
        /// <param name="numberFloor">
        /// The number of the floor where the enemy is.
        /// </param>
        /// <param name="speed">
        /// How fast the enemy can move himself.
        /// </param>
        /// <param name="timeToAttack">
        /// The time between two attacks of the enemy.
        /// </param>
        /// <param name="initialTime">
        /// The total game time when the zombie was created.
        /// </param>
        public Enemy(
            uint life, 
            string image, 
            uint numberFrames, 
            uint damage, 
            uint distance, 
            int numberFloor, 
            uint speed, 
            TimeSpan timeToAttack, 
            TimeSpan initialTime)
        {
            Contract.Requires(life > 0);
            Contract.Requires(damage > 0);
            Contract.Requires(numberFrames > 0);
            Contract.Requires(speed > 0);
            this.MaximumLife = life;
            this.Life = life;
            this.Image = image;
            this.damage = damage;
            this.Position = new Vector2(0, 0);
            this.Animation = new Animation();
            this.numberFrames = numberFrames;
            this.distance = distance;
            this.numberFloor = numberFloor;
            this.speed = speed;
            this.timeToAttack = timeToAttack;
            this.lastAttack = initialTime;
            this.Blood = new Animation();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets Animation.
        /// </summary>
        public Animation Animation { get; private set; }

        /// <summary>
        /// Gets blood.
        /// </summary>
        public Animation Blood { get; private set; }

        /// <summary>
        /// Gets DeathSoundName.
        /// </summary>
        public string DeathSoundName
        {
            get
            {
                return @"Sounds\ThrowUp";
            }
        }

        /// <summary>
        /// Gets Image.
        /// </summary>
        public string Image { get; private set; }

        /// <summary>
        /// Gets or sets Life.
        /// </summary>
        public uint Life
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() >= 0);
                return this.life;
            }

            set
            {
                Contract.Requires(value >= 0 && value <= this.MaximumLife);
                Contract.Ensures(this.Life >= 0);

                // If the enemy has been attacked show some blood.
                if (this.life > value)
                {
                    this.Blood.Active = true;
                }

                this.life = value;
                if (this.life == 0)
                {
                    this.Animation.Active = false;
                    this.DeathSound.Play();
                }
            }
        }

        /// <summary>
        /// Gets MaximumLife.
        /// </summary>
        public uint MaximumLife
        {
            get
            {
                Contract.Ensures(Contract.Result<uint>() > 0);
                return this.maximumLife;
            }

            private set
            {
                Contract.Requires(value > 0);
                this.maximumLife = value;
            }
        }

        /// <summary>
        /// Gets or sets Position.
        /// </summary>
        public Vector2 Position { get; set; }

        #endregion

        #region Properties

        /// <summary>
        /// Gets floor.
        /// </summary>
        private Floor Floor
        {
            get
            {
                return Client.Floors.Find(floor => floor.Active);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.Animation.Dispose();
            this.Blood.Dispose();
        }

        /// <summary>
        /// The draw.
        /// </summary>
        /// <param name="spriteBatch">
        /// The sprite batch.
        /// </param>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (this.AmIInTheScreen())
            {
                this.Animation.Draw(spriteBatch);
                this.Blood.Draw(spriteBatch);
            }
        }

        /// <summary>
        /// The load content.
        /// </summary>
        public void LoadContent()
        {
            this.Texture = Client.Content.Load<Texture2D>(this.Image);
            this.Animation.Initialize(this.Texture, Vector2.Zero, (int)this.numberFrames, Color.White, 1f, true, true);

            var bloodTexture = Client.Content.Load<Texture2D>("blood2");
            this.Blood.Initialize(bloodTexture, Vector2.Zero, 23, Color.White, 1f, false, true);
            this.Blood.Active = false;

            this.DeathSound = Client.Content.Load<SoundEffect>(this.DeathSoundName);
        }

        /// <summary>
        /// The update.
        /// </summary>
        /// <param name="gameTime">
        /// The game time.
        /// </param>
        /// <param name="position">
        /// The position.
        /// </param>
        public void Update(GameTime gameTime, Vector2 position)
        {
            this.lastScreenPosition = position;
            this.Move();

            // Transform the enemy position in the screen position.
            if (!this.AmIInTheScreen())
            {
                return;
            }

            this.Animation.Position = new Vector2(this.Position.X - Math.Abs(position.X), this.Position.Y);
            this.Blood.Position =
                new Vector2(
                    this.Animation.Position.X + (this.Animation.FrameWidth / 2) - (this.Blood.FrameWidth / 2), 
                    this.Animation.Position.Y + (this.Animation.FrameHeight / 2) - (this.Blood.FrameHeight / 2));
            this.Animation.Update(gameTime);
            this.Blood.Update(gameTime);
            this.Attack(gameTime);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The am i in the screen.
        /// </summary>
        /// <returns>
        /// The am i in the screen.
        /// </returns>
        private bool AmIInTheScreen()
        {
            return (this.Position.X + this.Animation.FrameWidth) >= Math.Abs(this.lastScreenPosition.X)
                   && this.Position.X <= (Math.Abs(this.lastScreenPosition.X) + Client.GraphicsDevice.Viewport.Width);
        }

        /// <summary>
        /// The attack.
        /// </summary>
        /// <param name="gameTime">
        /// The game Time.
        /// </param>
        private void Attack(GameTime gameTime)
        {
            if (gameTime.TotalGameTime - this.lastAttack <= this.timeToAttack)
            {
                return;
            }

            this.lastAttack = gameTime.TotalGameTime;
            Floor floor = Client.Floors.Find(auxFloor => this.numberFloor == auxFloor.Number);
            Player player = floor.NearestPlayer(this.Position);
            if (player != null && this.IsThePlayerNear(player))
            {
                player.Life -= this.damage <= player.Life ? this.damage : player.Life;
            }
        }

        /// <summary>
        /// The is the player near.
        /// </summary>
        /// <param name="player">
        /// The player.
        /// </param>
        /// <returns>
        /// The is the player near.
        /// </returns>
        /// Author: Juan Mompean Esteban
        private bool IsThePlayerNear(Player player)
        {
            return ((player.RealPosition.X <= this.Position.X
                     && (player.RealPosition.X + player.Animation.FrameWidth + this.distance) > this.Position.X)
                    ||
                    (player.RealPosition.X >= this.Position.X
                     && player.RealPosition.X < (this.Position.X + this.Animation.FrameWidth + this.distance)))
                   && player.RealPosition.Y < (this.Position.Y + this.Animation.FrameHeight)
                   && (player.RealPosition.Y + player.Animation.FrameHeight) > this.Position.Y;
        }

        /// <summary>
        /// The move.
        /// </summary>
        private void Move()
        {
            Player player = this.Floor.NearestPlayer(this.Position);
            if (player == null)
            {
                return;
            }

            if (player.RealPosition.X < this.Position.X)
            {
                this.Position = new Vector2(this.Position.X - this.speed, this.Position.Y);
                this.Animation.Flip(MovementsEnum.Left);
            }
            else if (player.RealPosition.X > (this.Position.X + (this.Animation.FrameWidth / 2)))
            {
                this.Position = new Vector2(this.Position.X + this.speed, this.Position.Y);
                this.Animation.Flip(MovementsEnum.Right);
            }
        }

        #endregion
    }
}